//  TCPCFSocket.m
//  Maxter
//
//  Created by bgg on Sat Nov 25 2000.
//  Copyright (c) 2000 Object Craft P/L. All rights reserved.

#import "TCPCFSocket.h"

RCS_ID("$Id: TCPCFSocket.m,v 1.13 2000/12/08 05:47:40 bgg Exp $");

// Based on work from Douglas Johnston <doug@timeinc.net>.  Understanding the
// CoreFoundation call backs was due to his efforts, bravo!

static void _tcpCFSocketCallback(CFSocketRef s, CFSocketCallBackType type,
                                 CFDataRef address, const void *data,
                                void *info);
const void *_tcpCFSocketRetainContext(const void *);
void _tcpCFReleaseSocketContext(const void *);


@implementation TCPCFSocket

- (void)dealloc
{
    if (_runLoopSource)
        [self removeFromRunLoop];
    [self _invalidateSocket];
    [super dealloc];
}


// action methods

- (void)dataAvailableWithNotification;
{
    [self _addToRunLoopWithCallBackType:kCFSocketReadCallBack];
}

- (void)acceptWithNotification
{
    [self _addToRunLoopWithCallBackType:kCFSocketDataCallBack];
}

- (void)readWithNotification
{
    [self _addToRunLoopWithCallBackType:kCFSocketDataCallBack];
}

// need to overload acceptConnection to close the socket fd held open
// by the CFSocket

- (void)acceptConnection
{
    [super acceptConnection];
    [self _invalidateSocket];
}

// XXX add more? -writeWithNotification, etc ...?

// remove our listener from the run loop.

- (void)removeFromRunLoop
{
    if (_runLoopSource == NULL)
        return;
    CFRunLoopRemoveSource([[NSRunLoop currentRunLoop] getCFRunLoop],       
                            _runLoopSource,
                            kCFRunLoopDefaultMode);
    if (CFRunLoopSourceIsValid(_runLoopSource))
        CFRunLoopSourceInvalidate(_runLoopSource);
    CFRelease(_runLoopSource);
    _runLoopSource = NULL;
}

// internal methods

// closes file descriptor in CFSocket

- (void)_invalidateSocket
{
    if (_cfSocketRef != NULL) {
        if (CFSocketIsValid(_cfSocketRef))
            CFSocketInvalidate(_cfSocketRef);
        CFRelease(_cfSocketRef);
        _cfSocketRef = NULL;
    }
}

// Add the CFSocket to the run loop this way:

- (void)_addToRunLoopWithCallBackType:(CFSocketCallBackType)callBackType
{
    _socketContext.version = 1;
    _socketContext.retain = &_tcpCFSocketRetainContext;
    _socketContext.release = &_tcpCFReleaseSocketContext;
    _socketContext.copyDescription = NULL;
    _socketContext.info = self;			// arg passed to call back

    _cfSocketRef = CFSocketCreateWithNative(NULL, [self socketFD], callBackType,
                                            &_tcpCFSocketCallback, &_socketContext);
    if (_runLoopSource != NULL) {
        [NSException raise:TCPCFSocketException 
                    format:@"_addToRunLoop:run loop active"];
    }
    _runLoopSource = CFSocketCreateRunLoopSource(NULL, _cfSocketRef, 0);
    CFRunLoopAddSource([[NSRunLoop currentRunLoop] getCFRunLoop], 
                        _runLoopSource,
                        kCFRunLoopDefaultMode);
}

// generate appropriate notification to alert caller that event has been
// recieved.

- (void)_processCallBackType:(CFSocketCallBackType)type withData:(NSData *)data
{
    NSDictionary *ui;
    ONTCPSocket *newSocket;
    NSNotificationCenter *defCenter = [NSNotificationCenter defaultCenter];

    switch (type) {
    case kCFSocketNoCallBack:		// no call back (how are we here?!)
        [NSException raise:TCPCFSocketArgumentException format:@"impossible call back"];
        break;
    case kCFSocketReadCallBack:		// data available or ready to accept
        [self removeFromRunLoop];
        [defCenter postNotificationName:TCPCFSocketDataAvailableNotification
                                 object:self];
        break;
    case kCFSocketAcceptCallBack:	// acception connected, new fd in data
        // XXX this is RONG!
        [NSException raise:TCPCFSocketException format:@"XXX not implemented (yet)"];
        // newSocket = [ONTCPSocket socketWithFD:*(const int *)[data bytes]];
        newSocket = nil;
        ui = [NSDictionary dictionaryWithObjectsAndKeys:
                                        newSocket, TCPCFSocketNotificationONTCPSocketItem,
                                        nil];
        [defCenter postNotificationName:TCPCFSocketAcceptedNotification
                                 object:self
                               userInfo:ui];
        break;
    case kCFSocketDataCallBack:		// data read from socket
        ui = [NSDictionary dictionaryWithObjectsAndKeys:
                                        data, TCPCFSocketNotificationDataItem,
                                        nil];
        [defCenter postNotificationName:TCPCFSocketReadCompletionNotification
                                 object:self
                               userInfo:ui];
        break;
    default:
        [NSException raise:TCPCFSocketArgumentException format:@"unknown CFSocket call back type: %d", type];
        break;
    }
}
@end


// call-back function from CFRunLoop to handle socket activity.  We convert
// the passed object into an NSData and then deal with those details by
// calling a method in the instance of TCPCFSocket which requested this
// callback; the instance's address is passed as "info".
//
// I suspect that data already is an "NSData *" but anyway we can't tell that
// from here.  Oh well.

static void
_tcpCFSocketCallback(CFSocketRef s, CFSocketCallBackType type, CFDataRef address,
                                        const void *data, void *info)
{
    NSData *dataObj;
    char *buf;

    if (data != NULL) {
        if ((buf = NSZoneMalloc(NSDefaultMallocZone(), CFDataGetLength(data))) == 0)
            [NSException raise:NSMallocException format:@"_tcpCFSocketCallback"];
        CFDataGetBytes(data, CFRangeMake(0, CFDataGetLength(data)), buf);
        dataObj = [NSData dataWithBytesNoCopy:buf length:CFDataGetLength(data)];
    } else
        dataObj = nil;

    [(TCPCFSocket *)info _processCallBackType:type withData:dataObj];
}

const void *
_tcpCFSocketRetainContext(const void *info)
{
    return info;
}

void
_tcpCFReleaseSocketContext(const void *info)
{
    // (do nothing)
}

NSString *TCPCFSocketException = @"TCPCFSocketException";
NSString *TCPCFSocketArgumentException = @"TCPCFSocketArgumentException";
NSString *TCPCFSocketDataAvailableNotification = @"TCPCFSocketDataAvailableNotification";
NSString *TCPCFSocketAcceptedNotification = @"TCPCFSocketAcceptedNotification";
NSString *TCPCFSocketReadCompletionNotification = @"TCPCFSocketReadCompletionNotification";
NSString *TCPCFSocketNotificationONTCPSocketItem = @"TCPCFSocketNotificationONTCPSocketItem";
NSString *TCPCFSocketNotificationDataItem = @"TCPCFSocketNotificationDataItem";